package p.ithorns.framework.idempotent;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;

/**
 * IdempotentAspect
 * 注解实现控制幂等请求
 * 问题：在拦截request的时候，需要获取请求参数，Get请求可以直接通过getParameterMap获取；
 * 但是Post请求获取不到，这里的方案是通过Request的InputStream;
 * 这里又有一个问题，就是通过注解拦截的时候，InputStream已经被Servlet读取过了。
 * Servlet中流只能读取一次， 怎么办？
 * 解决办法是HttpRequestServletWrapper;
 * 继承解决办法是HttpRequestServletWrapper，将Request的body“克隆”一下,后面都可以拿到里面的参数。
 *
 * @author ithorns
 * @version 1.0
 * @date 2019/4/30 13:03
 */
@Aspect
@EnableAspectJAutoProxy
public class IdempotentAspect {

    private final static Logger log = LoggerFactory.getLogger(IdempotentAspect.class);

    @Resource
    private StringRedisTemplate redis;

    /**
     * 拦截请求间隔时间
     */
    private final static long INTERVAL = 10 * 1000;

    public IdempotentAspect() {
        System.out.println("xxxxxxxxxxxxxx");
    }

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController) " +
            "|| @within(org.springframework.stereotype.Controller)")
    public void p1() {
    }

    @Pointcut("@annotation(p.ithorns.framework.idempotent.Idempotent)")
    public void p2() {
    }

    @Around(" p2()")
    public Object beforeRequest(ProceedingJoinPoint point) throws Throwable {
        HttpServletRequest request = getRequest();
        HttpServletResponse response = getResponse();

        Enumeration<String> enums = request.getParameterNames();
        StringBuilder sBuf = new StringBuilder();
        while (enums.hasMoreElements()) {
            String param = enums.nextElement();
            String paramValue = request.getParameter(param);
            sBuf.append(param).append("=").append(paramValue).append("&");
        }
        String key = md5Key(sBuf.toString());
        Boolean success = redis.opsForValue().setIfAbsent(key, "1", INTERVAL, TimeUnit.MILLISECONDS);
        if (Boolean.TRUE.equals(success)) {
            log.info("第1次请求");
            return point.proceed();
        } else {
            log.info("第N次请求");
            response.setCharacterEncoding("UTF-8");
            PrintWriter pw = response.getWriter();
            pw.write("请勿重复请求");
            return null;
        }
        //log.error("IdempotentAspect 执行失败", e);
    }

    private String md5Key(String str) throws NoSuchAlgorithmException {
        MessageDigest md5 = MessageDigest.getInstance("md5");
        return new String(md5.digest(str.getBytes()), StandardCharsets.UTF_8);
    }

    private HttpServletRequest getRequest() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        return request;
    }

    private HttpServletResponse getResponse() {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getResponse();
        return response;
    }

}
